package ss.pku.jsa.ui;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Map;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.DirectoryDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

import ss.pku.jsa.analysis.type.ConstraintError;
import ss.pku.jsa.analysis.type.ConstraintVisitor;
import ss.pku.jsa.analysis.type.DeclarationVisitor;
import ss.pku.jsa.analysis.type.TypeWorklist;
import ss.pku.jsa.analysis.type.om.ConstraintVariable;
import ss.pku.jsa.analysis.type.om.Location;
import ss.pku.jsa.analysis.type.om.OMShared;
import ss.pku.jsa.annotator.AnnotationASTVisitor;
import ss.pku.jsa.annotator.Configuration;
import ss.pku.jsa.annotator.ConfigurationReader;
import ss.pku.jsa.ast.ASTBaseVisitor;
import ss.pku.jsa.ast.ASTUtil;
import ss.pku.jsa.ast.Src;

/**
 * 
 * @author Bin
 * 
 * Main UI 
 *
 */
public class JSAExplorer extends Composite {

	private Text addressControl;

	private Tree treeControl;

	private StyledText textControl;
	private StyledText errorsTextControl;
	private StyledText nodeTextControl;

	private Button configButton;
	private Button srcButton;
	private Button analyseButton;

	private Color blue;
	private Color red;

	private Map<String, Src> srcs;

	private Configuration conf;

	public JSAExplorer(Composite parent, int style) {
		super(parent, style);

		GridLayout gridLayout = new GridLayout();
		super.setLayout(gridLayout);

		this.srcs = new Hashtable<String, Src>();
		this.conf = null;
		this.blue = getDisplay().getSystemColor(SWT.COLOR_BLUE);
		this.red = getDisplay().getSystemColor(SWT.COLOR_RED);

		Composite composite = new Composite(this, SWT.NONE);
		gridLayout = new GridLayout();
		gridLayout.numColumns = 4;
		composite.setLayout(gridLayout);
		composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

		this.addressControl = new Text(composite, SWT.BORDER);
		this.addressControl
				.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

		this.configButton = new Button(composite, SWT.PUSH);
		this.configButton.setText("Config");
		this.configButton.setLayoutData(new GridData(
				GridData.HORIZONTAL_ALIGN_END));
		this.configButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				Shell shell = ((Button) event.widget).getShell();
				DirectoryDialog dirDialog = new DirectoryDialog(shell,
						SWT.PRIMARY_MODAL);
				String dir = dirDialog.open();
				if (dir == null)
					return;
				config(dir);
			}
		});

		this.srcButton = new Button(composite, SWT.PUSH);
		this.srcButton.setText("SrcDir");
		this.srcButton
				.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
		this.srcButton.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent event) {
				Shell shell = ((Button) event.widget).getShell();
				DirectoryDialog dirDialog = new DirectoryDialog(shell,
						SWT.PRIMARY_MODAL);
				String dir = dirDialog.open();
				if (dir == null)
					return;
				addressControl.setText(dir);
				go();
			}
		});
		this.srcButton
		.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));

		this.analyseButton = new Button(composite, SWT.PUSH);
		this.analyseButton.setText("Analysis");
		this.analyseButton.addSelectionListener(new SelectionAdapter() {
			
			@Override
			public void widgetSelected(SelectionEvent e) {
				for (Src src : srcs.values()) {
					DeclarationVisitor dv = new DeclarationVisitor(
							OMShared.getConstraintTable(), src);
					src.accept(dv);
				}
				
				for (Src src : srcs.values()) {
					ConstraintVisitor cv = new ConstraintVisitor(
							OMShared.getConstraintTable(), src);
					src.accept(cv);
				}
				
				TypeWorklist worklist = new TypeWorklist(OMShared.getConstraintTable().values().toArray(new ConstraintVariable[0]));
				try{
					worklist.executeAnalysis();
				}catch(ConstraintError err){
					Location loc = err.getLocation();
					String fileName = loc.getFileName();
					Src src = srcs.get(fileName);
					textControl.setText(src.getSrc());
					StyleRange range = createRange(loc.getStartPosition(), loc.getLength(), red);
					textControl.setStyleRange(range);
					errorsTextControl.setText(err.getMessage());
				}
				
			}
		});
		
		SashForm sashFormVertical = new SashForm(this, SWT.VERTICAL);
		sashFormVertical.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL
				| GridData.GRAB_VERTICAL | GridData.HORIZONTAL_ALIGN_FILL
				| GridData.VERTICAL_ALIGN_FILL));

		SashForm sashFormHorizontal = new SashForm(sashFormVertical,
				SWT.HORIZONTAL);
		sashFormHorizontal.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL
				| GridData.GRAB_VERTICAL | GridData.HORIZONTAL_ALIGN_FILL
				| GridData.VERTICAL_ALIGN_FILL));

		// Source code viewer
		this.textControl = new StyledText(sashFormHorizontal, SWT.BORDER
				| SWT.READ_ONLY | SWT.H_SCROLL | SWT.V_SCROLL);

		this.textControl.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL
				| GridData.GRAB_VERTICAL | GridData.HORIZONTAL_ALIGN_FILL
				| GridData.VERTICAL_ALIGN_FILL));

		// AST tree viewer
		this.treeControl = new Tree(sashFormHorizontal, SWT.BORDER | SWT.SINGLE);
		this.treeControl.setLayoutData(new GridData(GridData.GRAB_VERTICAL
				| GridData.VERTICAL_ALIGN_FILL));
		this.treeControl.addSelectionListener(new SelectionAdapter() {
			public void widgetSelected(SelectionEvent e) {
				Tree tree = (Tree) e.widget;
				TreeItem[] items = tree.getSelection();
				TreeItem item = items[0];
				nodeTextControl.setText(item.getData(ASTBaseVisitor.NODE)
						.toString());

				while (item.getParentItem() != null) {
					item = item.getParentItem();
				}

				CompilationUnit cu = (CompilationUnit) item
						.getData(ASTBaseVisitor.NODE);
				IProblem[] problems = cu.getProblems();

				// first reset any previous selections
				StyleRange styleRange = new StyleRange();
				styleRange.start = 0;
				styleRange.length = textControl.getCharCount();
				textControl.setStyleRange(styleRange);
				int startSelection = -1;
				textControl.setText(srcs.get(item.getText()).getSrc());
				for (int i = 0; i < items.length; ++i) {
					ASTNode node = (ASTNode) items[i]
							.getData(ASTBaseVisitor.NODE);

					while (node != null && node.getLength() == 0) {
						node = node.getParent();
					}
					if (node != null) {
						styleRange = createRange(node.getStartPosition(),
								node.getLength(), blue);
						textControl.setStyleRange(styleRange);
						if (startSelection < 0
								|| styleRange.start < startSelection)
							startSelection = styleRange.start;
					}
				}
				if (startSelection >= 0)
					textControl.setSelection(startSelection);

				// select errors
				for (int i = 0; i < problems.length; ++i) {
					IProblem problem = problems[i];
					styleRange = createRange(problem.getSourceStart(),
							problem.getSourceEnd() - problem.getSourceStart()
									+ 1, red);
					textControl.setStyleRange(styleRange);
				}
//				errorsTextControl.setText("LDataTaintedExample/C:\\Users\\Bin\\Workspaces\\MyEclipse 9\\JavaForFun\\src\\DataTaintedExample\\SecurityExample~SecurityExample;.main()V#sql TAINTED\n" +
//						"LDataTaintedExample/C:\\Users\\Bin\\Workspaces\\MyEclipse 9\\JavaForFun\\src\\DataTaintedExample\\\\DatabaseAccessor~DatabaseAccessor;.getCriticalData(Ljava/lang/String;)Ljava/lang/String;#sql UNTAINTED");
			}
		});
		SashForm sashFormHorizontal2 = new SashForm(sashFormVertical,
				SWT.HORIZONTAL);
		sashFormHorizontal2.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL
				| GridData.GRAB_VERTICAL | GridData.HORIZONTAL_ALIGN_FILL
				| GridData.VERTICAL_ALIGN_FILL));

		this.errorsTextControl = new StyledText(sashFormHorizontal2, SWT.BORDER
				| SWT.READ_ONLY | SWT.H_SCROLL | SWT.V_SCROLL);
		this.errorsTextControl.setLayoutData(new GridData(
				GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL
						| GridData.HORIZONTAL_ALIGN_FILL
						| GridData.VERTICAL_ALIGN_FILL));

		this.nodeTextControl = new StyledText(sashFormHorizontal2, SWT.BORDER
				| SWT.READ_ONLY | SWT.H_SCROLL | SWT.V_SCROLL);
		this.nodeTextControl.setLayoutData(new GridData(
				GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL
						| GridData.HORIZONTAL_ALIGN_FILL
						| GridData.VERTICAL_ALIGN_FILL));

		sashFormVertical.setWeights(new int[] { 80, 20 });
	}

	static private StyleRange createRange(int start, int length, Color color) {
		StyleRange styleRange = new StyleRange();
		styleRange.start = start;
		styleRange.length = length;
		styleRange.fontStyle = SWT.BOLD;
		styleRange.foreground = color;
		return styleRange;
	}

	private void go() {
		try {
			SetFile(addressControl.getText());
		} catch (InterruptedException e) {
			reset();
		} catch (InvocationTargetException e) {
			reset();
			showError(e.getTargetException());
		} catch (Exception e) {
			reset();
			showError(e);
		}
	}

	private void config(String configDir) {
		this.conf = ConfigurationReader.get(new File(configDir));
	}

	private void reset() {
		this.treeControl.removeAll();
		this.textControl.setText("");
		this.errorsTextControl.setText("");
		this.nodeTextControl.setText("");
		this.srcs.clear();
	}

	private void SetFile(final String path) throws InterruptedException,
			InvocationTargetException {
		reset();
		ProgressMonitorDialog dialog = new ProgressMonitorDialog(getShell());
		dialog.run(true, true, new IRunnableWithProgress() {
			public void run(final IProgressMonitor monitor)
					throws InvocationTargetException {
				final ArrayList<Src> asts = ASTUtil.createASTs(path);
				final StringBuffer buffer = new StringBuffer();
				getDisplay().syncExec(new Runnable() {
					public void run() {
						for (Src src : asts) {
							srcs.put(src.getFileAbsolutePath(), src);
							ASTVisitor visitor = new ASTBaseVisitor(
									treeControl, monitor, src.getFileAbsolutePath());
							visitor = JSAExplorer.this.conf == null ? visitor
									: new AnnotationASTVisitor(visitor,
											JSAExplorer.this.conf, src);

							CompilationUnit cu = (CompilationUnit) src
									.getAstTree();
							cu.accept(visitor);
							if (!monitor.isCanceled()) {
								IProblem[] problems = cu.getProblems();
								if (problems != null && problems.length > 0) {
									for (int i = 0; i < problems.length; ++i) {
										IProblem problem = problems[i];
										String message = String.format(
												"%s : %s line: %s\n",
												src.getFileName(),
												problem.getMessage(),
												problem.getSourceLineNumber());
										buffer.append(message);
									}
								}
							}
						}
						if (buffer.length() > 0)
							errorsTextControl.setText(buffer.toString());
					}
				});
			}
		});
	}

	public void setLayout(Layout layout) {
	}

	private void showError(Throwable e) {
		MessageBox msgBox = new MessageBox(addressControl.getShell(),
				SWT.APPLICATION_MODAL | SWT.OK | SWT.ICON_ERROR);
		msgBox.setText("JSA Explorer Error");
		String msg = e.getMessage();
		if (null == msg)
			msg = e.toString();

		msgBox.setMessage(msg);
		msgBox.open();
	}

	public static void main(String[] args) {
		Display display = new Display();
		Shell shell = new Shell(display);
		shell.setText("JSA Explorer");
		shell.setLayout(new FillLayout());
		new JSAExplorer(shell, SWT.NONE);
		final Point minimum = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
		shell.addControlListener(new ControlAdapter() {
			public void controlResized(ControlEvent e) {
				Shell shell = (Shell) e.widget;
				Point size = shell.getSize();
				boolean change = false;
				if (size.x < minimum.x) {
					size.x = minimum.x;
					change = true;
				}
				if (size.y < minimum.y) {
					size.y = minimum.y;
					change = true;
				}
				if (change)
					shell.setSize(size);
			}
		});
		shell.open();

		while (!shell.isDisposed()) {
			if (!display.readAndDispatch())
				display.sleep();
		}
		display.dispose();
	}
}